/* Copyright 2009 Requirements Management System 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mags.remas.view.widgets;

import java.util.ArrayList; 
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerRow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.mags.remas.control.commons.Cloner;
import org.mags.remas.control.commons.ControlUtils;
import org.mags.remas.control.commons.IAction;
import org.mags.remas.control.commons.ICrudActions;
import org.mags.remas.exception.SingletonExceptionHandler;
import org.mags.remas.view.commons.ImageProvider;

/**
 * 
 * @author Mario Gaitan
 *
 */
public class ListViewer extends TableViewer 
	implements ICellModifier, IStructuredContentProvider, ITableLabelProvider {	
	private ICrudActions crudActions;
	private Image elementImage;
	private IAction expandActionListener;
	private int oldTextHeight = -1;
	private int oldTextWidth = -1;
	private boolean autoSizeText = true;
	final private String[] properties;
	final private String[] titles;
	private String[] labels;
	private Map<String, Object[]> comboValues;
	
	public Map<String, Object[]> getComboValues() {
		return comboValues;
	}
	public void setComboValues(Map<String, Object[]> comboValues) {
		this.comboValues = comboValues;
	}
	public String[] getLabels() {
		return labels;
	}
	public void setLabels(String[] labels) {
		this.labels = labels;
	}
	public String[] getProperties() {
		return properties;
	}
	public boolean isAutoSizeText() {
		return autoSizeText;
	}
	private void resizeAreaText(Text text) {
		String t = text.getText();
		GC gc = new GC(text);
		Point parentSize = super.getTable().getSize();
		Point size = gc.textExtent(t);
		int w = text.getBounds().width;
		int h = text.getBounds().height;
		if(size.y>=oldTextHeight&&size.y<=parentSize.y-text.getBounds().y-25)
			h = size.y+15;
		if(size.x>=oldTextWidth&&size.x<=parentSize.x-text.getBounds().x-25)
			w = size.x+15;
		text.setSize(w, h);
	}	
	public IAction getExpandActionListener() {
		return expandActionListener;
	}
	public void setExpandActionListener(IAction expandActionListener) {
		this.expandActionListener = expandActionListener;
	}
	public void setElementImage(Image elementImage) {
		this.elementImage = elementImage;
	}
	public void exceptionCatched(Exception e) {
		if(!ignoreException(e))
			SingletonExceptionHandler.getInstance().showException(e);
	}			
	public boolean ignoreException(Exception e) {
		return false;
	}	
	public ICrudActions getCrudActions() {
		return crudActions;
	}
	public void setCrudActions(ICrudActions crudActions) {
		this.crudActions = crudActions;
	}
	private Action createAction = new Action() {
		@Override
		public ImageDescriptor getImageDescriptor() {
			return ImageProvider.getCommonImageDescriptor("new.png");
		}
		@Override
		public void run() {
			create();
		}
		@Override
		public String getText() {
			return "new";
		}
	};	
	private Action deleteAction = new Action() {
		@Override
		public ImageDescriptor getImageDescriptor() {
			return ImageProvider.getCommonImageDescriptor("delete.png");
		}		
		@Override
		public void run() {
			delete();
		}
		@Override
		public String getText() {
			return "delete";
		}
	};
	private Action editAction = new Action() {
		@Override
		public ImageDescriptor getImageDescriptor() {
			return ImageProvider.getCommonImageDescriptor("edit.png");
		}		
		@Override
		public void run() {
			edit();
		}
		@Override
		public String getText() {
			return "edit";
		}
	};
	private Action refreshAction = new Action() {
		@Override
		public ImageDescriptor getImageDescriptor() {
			return ImageProvider.getCommonImageDescriptor("refresh.png");
		}		
		@Override
		public void run() {
			refresh();
		}
		@Override
		public String getText() {
			return "refresh";
		}		
	};
	private Action expandAction = new Action() {
		@Override
		public ImageDescriptor getImageDescriptor() {
			return ImageProvider.getCommonImageDescriptor("expand.png");
		}		
		@Override
		public void run() {
			expand();
		}
		@Override
		public String getText() {
			return "expand";
		}				
	};
	public void setExpandAction(Action expandAction) {
		this.expandAction = expandAction;
	}
	public Action getExpandAction() {
		return expandAction;
	}
	public Action getCreateAction() {
		return createAction;
	}
	public void setCreateAction(Action createAction) {
		this.createAction = createAction;
	}
	public Action getDeleteAction() {
		return deleteAction;
	}
	public void setDeleteAction(Action deleteAction) {
		this.deleteAction = deleteAction;
	}
	public Action getEditAction() {
		return editAction;
	}
	public void setEditAction(Action editAction) {
		this.editAction = editAction;
	}
	public Action getRefreshAction() {
		return refreshAction;
	}
	public void setRefreshAction(Action refreshAction) {
		this.refreshAction = refreshAction;
	}	
	private void expand() {
		try {
			if(expandActionListener!=null)
				expandActionListener.execute(this);
		} catch(Exception e) {
			exceptionCatched(e);
		}				
	}
	private void edit() {
		try {
			Object element = getSelectedElement();
			if(element==null)
				throw new RuntimeException("there is no selected element");
			super.editElement(element, 0);
		} catch(Exception e) {
			exceptionCatched(e);
		}
	}	
	public void create() {
		try {
			if(crudActions!=null) {
				Object newElement = crudActions.create(this);
				if(newElement!=null) {
					super.add(newElement);
					super.editElement(newElement, 0);
				}
			}
		} catch(Exception e) {
			exceptionCatched(e);
		}
	}
	@SuppressWarnings("rawtypes")
	public void delete() {
		try {
			if(crudActions!=null) {
				List elements = getSelectedElements();
				if(elements==null||elements.size()==0)
					throw new RuntimeException("there is no selected element");
				if(MessageDialog.openConfirm(null, "confirm...", "Are you want to delete the elements?")) {
					for(Object element: elements) {
						crudActions.delete(element);
						super.remove(element);
					}
				}
			}
		} catch(Exception e) {
			exceptionCatched(e);
		}
	}
	@Override
	public void modify(Object element, String property, Object value) {
		try {
			if(crudActions!=null) {
				TableItem widget = (TableItem)element;
				Object cloned = Cloner.clone(widget.getData());
				CellEditor cellEditor = getCellEditor(property);
				Object oldValue = ControlUtils.getProperty(widget.getData(), property);
				Object newValue = value;
				if(cellEditor instanceof ComboBoxCellEditor) {
					Object[] values = comboValues.get(property);
					newValue = values[(Integer)value];
				} 
				if(oldValue==null||newValue==null||!oldValue.equals(newValue)) {
					ControlUtils.setProperty(cloned, property, newValue);
					crudActions.update(cloned);
					super.doUpdateItem(widget, cloned, true);
				}
			}
		} catch(Exception e) {
			exceptionCatched(e);
		}
	}
	@Override
	public Object getValue(Object element, String property) {
		CellEditor cellEditor = getCellEditor(property);
		if(cellEditor instanceof TextCellEditor) {
			String _property = (String)ControlUtils.getPropertyAsString(element, property);
			return _property==null?"":_property;
		}
		else if(cellEditor instanceof ComboBoxCellEditor) {
			if(comboValues==null)
				return ((Number)ControlUtils.getProperty(element, property)).intValue();
			else {
				Object[] values = comboValues.get(property);
				int i = 0;
				Object value = ControlUtils.getProperty(element, property);
				while(i<=values.length-1 && !values[i].equals(value))
					i++;
				if(values[i].equals(value))
					return i;
				else
					return -1;
			}
		}
		else 
			return ControlUtils.getProperty(element, property);
	}	
	@SuppressWarnings("rawtypes")
	@Override
	public Object[] getElements(Object inputElement) {
		try {
			if(crudActions!=null) {
				List elements =  (List)crudActions.getObjects(this);
				if(elements!=null&&elements.size()>0)
					return elements.toArray();
			}
		} catch(Exception e) {
			exceptionCatched(e);
		}
		return new Object[0];
	}	
	private void loadControls() {
		super.getTable().setHeaderVisible(true);
		super.getTable().setLinesVisible(true);
		super.setContentProvider(this);
		super.setLabelProvider(this);
		super.setColumnProperties(this.properties);
		super.setCellModifier(this);
		final CellEditor[] editors = new CellEditor[this.properties.length];
		super.getTable().addKeyListener(new KeyListener() {
			@Override
			public void keyPressed(KeyEvent e) {
				if(e.keyCode==SWT.F2)
					edit();
				else if(e.keyCode==SWT.DEL)
					delete();
				else if ((e.stateMask == SWT.CTRL) && (e.keyCode=='N'||e.keyCode=='n'))
					getControl().getMenu().setVisible(true);								
			}
			@Override
			public void keyReleased(KeyEvent e) {
			}
		});		
		
		for(int i=0;i<=properties.length-1;i++) {
			TableColumn column = new TableColumn(super.getTable(), SWT.LEFT);
			column.setText(titles[i]);
			if(i==0)
				column.setWidth(720);
			else
				column.setWidth(100);
			column.setResizable(true);
			column.setMoveable(false);
		}
		
		GridData gridDataDescription = new GridData();
		gridDataDescription.horizontalAlignment = SWT.FILL;
		gridDataDescription.grabExcessHorizontalSpace = true;
		gridDataDescription.verticalAlignment = SWT.FILL;	
		gridDataDescription.verticalSpan = 4;
		gridDataDescription.grabExcessVerticalSpace=true;	
		
		if(!autoSizeText)
			editors[0] = new TextCellEditor(this.getTable(), SWT.MULTI|SWT.V_SCROLL|SWT.BORDER);
		else 
			editors[0] = new TextCellEditor(this.getTable(), SWT.MULTI|SWT.BORDER);
		
		final Text text = (Text)editors[0].getControl();
		text.setTextLimit(255);
		editors[0].getControl().addFocusListener(new FocusListener() {
			@Override
			public void focusGained(FocusEvent e) {
				if(autoSizeText) {
					oldTextHeight = text.getSize().y;
					oldTextWidth = text.getSize().x;
					resizeAreaText(text);
				}
			}
			@Override
			public void focusLost(FocusEvent e) {
			}
		});		
		
		editors[0].getControl().setLayoutData(gridDataDescription);
		editors[0].getControl().addKeyListener(new KeyListener() {
			@Override
			public void keyPressed(KeyEvent e) {
				if(e.keyCode==SWT.TAB) {
					editors[0].getControl().traverse(SWT.TRAVERSE_TAB_NEXT);
					e.doit = false;
				}
				if(autoSizeText)
					resizeAreaText(text);
			}
			@Override
			public void keyReleased(KeyEvent e) {
			}
		});		
		this.setCellEditors(editors);
		final Color evenColor = new Color(PlatformUI.getWorkbench().getDisplay(), 235, 235, 235);
		final Color oddColor = new Color(PlatformUI.getWorkbench().getDisplay(), 255, 255, 255);
		Listener paintListener = new Listener() {
			public void handleEvent(Event event) {
				switch (event.type) {
					case SWT.MeasureItem: {
						if(event.index==0) {
							TableItem item = (TableItem) event.item;
							String text = item.getText(event.index);
							Point size = event.gc.textExtent(text);
							event.width = size.x;
							event.height = Math.max(event.height, size.y);
						}
						break;
					}
					case SWT.PaintItem: {
						TableItem item = (TableItem) event.item;
						ViewerRow row = ListViewer.this.getViewerRowFromItem(item);
						if(event.index==0) {
							String text = item.getText(event.index);
							Point size = event.gc.textExtent(text);
							int offset2 = event.index == 0 ? Math.max(0, (event.height - size.y) / 2) : 0;
							event.gc.drawImage(item.getImage(event.index), event.x, event.y);
							event.gc.drawText(text, event.x+17, event.y + offset2, true);
						}
						ViewerRow belowRow = row.getNeighbor(ViewerRow.ABOVE, false);
						if(belowRow==null)
							row.setBackground(event.index, evenColor);
						else {
							Color color = belowRow.getBackground(event.index);
							if(color.getBlue()==evenColor.getBlue()
							&& color.getGreen()==evenColor.getGreen()
							&& color.getRed()==evenColor.getRed()) {
								row.setBackground(event.index, oddColor);		        			
							} else {
								row.setBackground(event.index, evenColor);		        			
							}
						}
						break;
					}
					case SWT.EraseItem: {
						if(event.index==0) 
							event.detail &= ~SWT.FOREGROUND;
						break;
					}
				}
			}
		};		
	    this.getTable().addListener(SWT.MeasureItem, paintListener);
	    this.getTable().addListener(SWT.PaintItem, paintListener);
	    this.getTable().addListener(SWT.EraseItem, paintListener);
	    MenuManager menuMgr = new MenuManager("#PopupMenu");
		menuMgr = new MenuManager("#PopupMenu");
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				createPopupMenu(manager);
			}
		});
		Menu menuRightClick = menuMgr.createContextMenu(this.getControl());
		this.getControl().setMenu(menuRightClick);		
	}
	protected void createPopupMenu(IMenuManager manager) {
		manager.add(createAction);
		manager.add(editAction);
		manager.add(deleteAction);
		manager.add(refreshAction);		
	}
	public ListViewer(Composite parent, int style, String[] properties, String[] titles, final boolean autoSizeText) {
		super(parent, style | (SWT.BORDER |SWT.V_SCROLL|SWT.H_SCROLL|SWT.FULL_SELECTION|SWT.MULTI));
		this.properties = properties;
		this.autoSizeText = autoSizeText;
		this.titles = titles;
		loadControls();
	}
	public CellEditor getCellEditor(String property) {
		CellEditor[] editors = super.getCellEditors();
		int i = 0;
		while(i<=properties.length-1 && !StringUtils.equals(properties[i], property))
			i++;
		if(i<=editors.length)
			return editors[i];
		return null;
	}
	@Override
	public boolean canModify(Object element, String property) {
		return (getCellEditor(property)!=null);
	}
	@Override
	public void dispose() {
	}
	@Override
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
	}
	@Override
	public Image getColumnImage(Object element, int columnIndex) {
		if(columnIndex==0) {
			if(elementImage==null)
				elementImage = ImageProvider.getCommonImage("value.png");
			return elementImage;
		} else
			return null;
	}
	@Override
	public void addListener(ILabelProviderListener listener) {
	}
	@Override
	public boolean isLabelProperty(Object element, String property) {
		return true;
	}
	@Override
	public void removeListener(ILabelProviderListener listener) {
	}
	@Override
	public String getColumnText(Object element, int columnIndex) {
		String label = properties[columnIndex];
		if(labels!=null)
			label = labels[columnIndex];
		String text=(String)ControlUtils.getPropertyAsString(element, label);
		if(text==null)text="";
		return text;
	}	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List getSelectedElements() {
		ISelection selection = super.getSelection();
		if(selection!=null && selection instanceof IStructuredSelection) {
			Iterator iterator = ((IStructuredSelection)selection).iterator();
			List list=new ArrayList();
			while(iterator.hasNext()) {
				Object i = iterator.next();
				list.add(i);
			}
			return list;
		}
		return null;
	}		
	public Object getSelectedElement() {
		ISelection selection = super.getSelection();
		if(selection!=null && selection instanceof IStructuredSelection) {
			IStructuredSelection structuredSelection = (IStructuredSelection)selection;
			if(structuredSelection.isEmpty())
				return null;
			else {
				Object first = structuredSelection.getFirstElement();
				return first;
			}
		}
		else
			return null;
	}				
}
